package com.zws.cucumber.glue;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import com.zws.cucumber.util.colorfullog.ZwsPrintTableHelper;
import com.zws.cucumber.util.colorfullog.table.ZwsFixedPrefixRender;
import com.zws.cucumber.var.ZwsVar;
import com.zws.cucumber.var.ZwsVarBlock;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.en.Given;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;

import java.util.*;
import java.util.stream.Collectors;

import static com.zws.cucumber.expander.ZwsStr.expand;
import static com.zws.cucumber.expr.ZwsExpr.evaluate;
import static com.zws.cucumber.expr.ZwsExpr.evaluateSilently;

public class ZwsVariableStepDef {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsVariableStepDef.class);

    @Before(order = 0)
    @After
    public void beforeAndAfter() {
        // clear variables
        ZwsVar.clearAllVariables();
    }

    /*
     * Define Global (No Expanding & No Evaluation)
     */
    @Given("define global {word} as {string}")
    public void defineGlobal(String variableName, String value) {
        ZwsVar.putGlobal(variableName, value);
    }

    @Given("define global {word} as")
    public void defineGlobalByDocString(String variableName, String value) {
        defineGlobal(variableName, value);
    }

    @Given("define global as follows")
    public void defineGlobalAsFollows(DataTable dataTable) {
        dataTable.cells().forEach(row -> defineGlobal(row.get(0), row.get(1)));
    }

    /*
     * Define Scenario / Procedure (No Expanding & No Evaluation)
     */
    @Given("define {word} as {string}")
    public void define(String variableName, String value) {
        ZwsVar.put(variableName, value);
    }


    @Given("define {word} as")
    public void defineByDocString(String variableName, String value) {
        define(variableName, value);
    }

    @Given("define as follows")
    public void defineAsFollows(DataTable dataTable) {
        dataTable.cells().forEach(row -> define(row.get(0), row.get(1)));
    }

    /*
     * Define Scenario / Procedure After Expanding
     */

    @Given("define {word} after expanding as {string}")
    public void defineAfterExpanding(String targetName, String string) {
        ZwsVar.put(targetName, expand(string));
    }

    @Given("define {word} by expanding string {string}")
    public void defineAfterExpanding2(String targetName, String string) {
        defineAfterExpanding(targetName, string);
    }

    @Given("define {word} after expanding as")
    public void defineAfterExpandingByDocString(String targetName, String string) {
        ZwsVar.put(targetName, expand(string));
    }

    @Given("define {word} after expanding as follows")
    public void defineAfterExpandingAsFollows(DataTable dataTable) {
        dataTable.cells().forEach(row -> defineAfterExpanding(row.get(0), expand(row.get(1))));
    }

    @Given("define {word} after expanding as {string} with arguments")
    public void defineAfterExpandingWithArguments(String targetName, String string, DataTable arguments) {
        Map<String, Object> argumentMap = arguments.asMap(String.class, Object.class);
        argumentMap = argumentMap.entrySet()
                .stream()
                .map(e -> new AbstractMap.SimpleEntry<String, String>(e.getKey(), evaluateSilently(e.getKey(), expand((String) e.getValue()))))
                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
        ZwsVar.put(targetName, expand(string, argumentMap));
    }

    @Given("define {word} by expanding string {string} with arguments")
    public void defineAfterExpandingWithArguments2(String targetName, String string, DataTable arguments) {
        defineAfterExpandingWithArguments(targetName, string, arguments);
    }

    /*
     * Define Scenario / Procedure After Expanding Variable
     */

    @Given("define {word} after expanding variable {string}")
    public void defineAfterExpandingVariable(String targetName, String sourceName) {
        ZwsVar.put(targetName, expand(ZwsVar.get(sourceName)));
    }

    @Given("define {word} by expanding variable {string}")
    public void defineAfterExpandingVariable2(String targetName, String sourceName) {
        defineAfterExpandingVariable(targetName, sourceName);
    }

    @Given("define {word} after expanding variable {string} with arguments")
    public void defineByExpandingVariableWithArguments(String targetName, String sourceName, DataTable arguments) {
        Map<String, Object> argumentMap = arguments.asMap(String.class, Object.class);
        argumentMap = argumentMap.entrySet()
                .stream()
                .map(e -> new AbstractMap.SimpleEntry<String, String>(e.getKey(), evaluateSilently(e.getKey(), expand((String) e.getValue()))))
                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
        ZwsVar.put(targetName, expand(ZwsVar.get(sourceName), argumentMap));
    }

    @Given("define {word} by expanding variable {string} with arguments")
    public void defineByExpandingVariableWithArguments2(String targetName, String sourceName, DataTable arguments) {
        defineByExpandingVariableWithArguments(targetName, sourceName, arguments);
    }


    @Given("define {word} from variable {string} with modifications")
    public void defineFromVariable(String targetName, String sourceName, DataTable replacements) {
        String value = ZwsVar.get(sourceName);
        DocumentContext documentContext = JsonPath.parse(value);
        for (List<String> replacement : replacements.asLists()) {
            String op = replacement.get(0);
            JsonPath path = JsonPath.compile(replacement.get(1));
            String newVal = replacement.get(2);
            switch (op) {
                case "-" -> documentContext.delete(path);
                case "*" -> documentContext.set(path, expand(newVal));
                case "+" -> documentContext.add(path, expand(newVal));
                default -> throw new IllegalArgumentException(op);
            }
        }
        ZwsVar.put(targetName, documentContext.jsonString());
    }

    @Given("define {word} from variable {string} with replacements")
    public void defineFromVariable2(String targetName, String sourceName, DataTable replacements) {
        defineFromVariable(targetName, sourceName, replacements);
    }


    /*
     * Define Scenario / Procedure After Evaluation
     */
    @Given("define {word} after evaluation as {string}")
    public void defineAfterEvaluation(String variableName, String value) {
        ZwsVar.put(variableName, evaluate(variableName, value));
    }

    @Given("define {word} after evaluation as")
    public void defineAfterEvaluationByDocString(String variableName, String value) {
        defineAfterEvaluation(variableName, value);
    }

    @Given("define after evaluation as follows")
    public void defineAfterEvaluationAsFollows(DataTable dataTable) {
        dataTable.cells().forEach(row -> defineAfterEvaluation(row.get(0), expand(row.get(1))));
    }

    /*
     * Define Scenario / Procedure After Expanding And Evaluation
     */
    @Given("define {word} after expanding and evaluation as {string}")
    public void defineAfterExpandingAndEvaluation(String variableName, String value) {
        ZwsVar.put(variableName, evaluate(variableName, expand(value)));
    }

    @Given("define {word} after expanding and evaluation as")
    public void defineAfterExpandingAndEvaluationByDocString(String variableName, String value) {
        defineAfterEvaluation(variableName, expand(value));
    }

    @Given("define after expanding and evaluation as follows")
    public void defineAfterExpandingAndEvaluationAsFollows(DataTable dataTable) {
        dataTable.cells().forEach(row -> defineAfterEvaluation(row.get(0), expand(row.get(1))));
    }

    /*
     * Print
     */
    @Given("print variables")
    public void printVariables() {
        List<String> keyList = Arrays.asList("Key", "Type", "Value");

        StringBuilder sb = new StringBuilder("\n");
//        ZwsFixedPrefixRender render = new ZwsFixedPrefixRender(ZwsProcedureLoggerLayout.ProcedureDepthConverter.Slashes[ZwsProcedureUtil.invokingLevel()]);
        ZwsFixedPrefixRender render = new ZwsFixedPrefixRender("");
        Iterator<ZwsVarBlock> blockIterator = ZwsVar.varBlockIterator();
        while (blockIterator.hasNext()) {
            ZwsVarBlock block = blockIterator.next();
            List<Map<String, Object>> rowList = block.getVariables().entrySet()
                    .stream()
                    .map(e -> {
                        Map<String, Object> map = new HashMap<>();
                        map.put("Key", e.getKey());
                        map.put("Type", e.getValue() != null ? e.getValue().getClass().getSimpleName() : "[null]");
                        map.put("Value", e.getValue());
                        return map;
                    })
                    .collect(Collectors.toList());

            sb.append(
                    ZwsPrintTableHelper.makeTableString(
                            "Variables " + block.getScope() + " " + (block.getContext() != null ? block.getContext().contextDescription() : ""),
                            Pair.of(keyList, rowList),
                            render));
            sb.append("\n");
            sb.append(render.render());
            sb.append("\n");
        }
        logger.debug(sb.toString());
    }
}
